package org.softmed.filehandling;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.softmed.filehandling.selectors.FileSelector;

public class ClassFinder {

	List<FileSelector> fileSelectors = new ArrayList<FileSelector>();

	public ClassFinder() {

	}

	public ClassFinder(FileSelector... selectors) {
		for (FileSelector fileSelector : selectors) {
			fileSelectors.add(fileSelector);
		}

	}

	public Collection<Class> getClasses(String pckgname) throws Throwable {
		FileUtil util = new FileUtil();

		List<Class> classes = new ArrayList<Class>();
		// Get a File object for the package
		File directory = null;
		try {
			ClassLoader cld = this.getClass().getClassLoader();// Thread.

			if (cld == null) {
				throw new ClassNotFoundException("Can't get class loader.");
			}
			String path = pckgname.replace('.', '/');
			Enumeration<URL> resources = cld.getResources(path);

			URL resource = null;
			while (resources.hasMoreElements()) {
				resource = resources.nextElement();
				processResource(resource, pckgname, classes);
			}

			return classes;

		} catch (NullPointerException x) {
			throw new ClassNotFoundException(pckgname + " (" + directory
					+ ") does not appear to be a valid package");
		}
	}

	public void processResource(URL resource, String pckgname,
			List<Class> classes) throws Throwable {
		System.out.println("Resource Path = " + resource.getPath());

		String path = resource.getPath();
		path = path.replaceAll("%20", " ");

		if (path.startsWith("file:/"))
			path = path.substring(5);

		int index = path.indexOf('!');

		if (index >= 0) {
			// this is a jar
			String jarPath = path.substring(0, index);
			System.out.println("JAR PATH : " + jarPath);
			JarFile currentFile = new JarFile(jarPath);
			pckgname = pckgname.replace('.', '/') + "/";
			getClassesInPackageInJarFile(classes, pckgname, currentFile);

		} else {
			// it is a directory

			System.out.println("Getting Classes Dir for BD: " + path);
			File directory = new File(path);

			// test the directory
			if (directory.exists()) {
				// Get the list of the files contained in the package
				String[] files = directory.list();
				FileSelector fileSelector = null;
				String filePath = null;
				try {
					for (int i = 0; i < files.length; i++) {
						filePath = files[i];
						processFile(pckgname, classes, filePath);
					}
				} catch (Throwable t) {
					t.printStackTrace();
					System.out.println("Canceled Class Finding");

				}

				// List<Class> jarClasses = getClassesFromFileJarFile(pckgname,
				// util.getRootPath());
				// classes.addAll(jarClasses); //

			} else
				System.out.println("Couldn't find directory : " + directory);
		}
	}

	// go through the file selector list and check if any one of them
	// work for the file
	private void processFile(String pckgname, List<Class> classes,
			String filePath) throws Throwable {
		FileSelector fileSelector;
		for (int k = 0; k < fileSelectors.size(); k++) {
			fileSelector = fileSelectors.get(k);
			if (fileSelector.isFileSelected(filePath, pckgname)) {
				Class type = fileSelector.processFile(filePath, pckgname);
				classes.add(type);
				System.out.println("Adding class from directory :" + type);
				break;
			}
		}
		//
	}

	public List<Class> getClassesFromFileJarFile(String pckgname,
			String baseDirPath) throws Throwable {
		ArrayList<Class> classes = new ArrayList<Class>();

		String path = pckgname.replace('.', '/') + "/";
		File mF = new File(baseDirPath);
		String[] files = mF.list();

		ArrayList jars = new ArrayList();

		for (int i = 0; i < files.length; i++)
			if (files[i].endsWith(".jar"))
				jars.add(files[i]);

		for (int i = 0; i < jars.size(); i++) {
			try {
				JarFile currentFile = new JarFile(jars.get(i).toString());
				getClassesInPackageInJarFile(classes, path, currentFile);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return classes;
	}

	protected void getClassesInPackageInJarFile(Collection<Class> classes,
			String packageName, JarFile jarFile) throws Throwable {

		for (Enumeration e = jarFile.entries(); e.hasMoreElements();) {
			JarEntry jarEntry = (JarEntry) e.nextElement();
			// System.out.println("jarEntry : " + jarEntry);
			// System.out.println("path : " + packageName);
			if (jarEntry.getName().length() > packageName.length()
					&& jarEntry.getName().substring(0, packageName.length())
							.equals(packageName)) {
				// only add the classes in the same package
				// not in sub packages
				// System.out.println("same package jarEntry 1:" + jarEntry);
				String rest = jarEntry.getName()
						.substring(packageName.length());
				int index = rest.lastIndexOf('.');
				if (index < 0)
					continue;

				rest = rest.substring(0, index);
				if (!rest.contains("/")) {
					// System.out.println("same package jar entry 2 for sure:"
					// + jarEntry);
					selectClassesInJarPackage(jarFile, jarEntry, packageName,
							classes);
				}
				// System.out.println(rest);

				// && jarEntry.getName().endsWith(".class")
				//				
				//				
				// classes.add(Class.forName(jarEntry.getName().replaceAll(
				// "/", ".").replace(".class", "")));
			}
		}
	}

	private void selectClassesInJarPackage(JarFile jarFile, JarEntry jarEntry,
			String path, Collection<Class> classes) throws Throwable {
		FileSelector fileSelector;
		for (int k = 0; k < fileSelectors.size(); k++) {
			fileSelector = fileSelectors.get(k);
			Class type = fileSelector.processJarFile(jarFile, jarEntry, path);
			if (type != null) {
				System.out.println("Adding class from jar: " + type);
				classes.add(type);
			}
			break;
		}
	}

}
